#include "SharedMemoryImpl.h"

#if !defined(_WIN32) && !defined(_WIN64)
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <errno.h>

#ifndef F_ADD_SEALS
#define F_ADD_SEALS 1033
#define F_GET_SEALS 1034
#define F_SEAL_SEAL 0x0001
#define F_SEAL_SHRINK 0x0002
#define F_SEAL_GROW 0x0004
#define F_SEAL_WRITE 0x0008
#endif

#define EINTR_LOOP(var, cmd) \
    do                       \
    {                        \
        var = cmd;           \
    } while (var == -1 && errno == EINTR)

static inline int
safe_close(int fd)
{
    int ret;
    EINTR_LOOP(ret, ::close(fd));
    return ret;
}

class SharedMemoryImpl_Posix : public SharedMemoryImpl
{
public:
    explicit SharedMemoryImpl_Posix(const std::string &key);
    ~SharedMemoryImpl_Posix();
    bool Create(size_t size) override;
    bool Attach(int permissions) override;
    bool Detach() override;
    bool IsAttached() const override;

    int Write(unsigned char *data, size_t size) override;
    int Read(unsigned char *buf, size_t size) override;

protected:
    int Handle();
    bool CleanHandle();

private:
    int hand = 0;
};

SharedMemoryImpl_Posix::SharedMemoryImpl_Posix(const std::string &key)
    : SharedMemoryImpl(key)
{
}

SharedMemoryImpl_Posix::~SharedMemoryImpl_Posix()
{
    CleanHandle();
}

bool SharedMemoryImpl_Posix::Create(size_t size)
{
    if (!Handle())
        return false;

    int fd;
#ifdef O_CLOEXEC
    // First try with O_CLOEXEC flag, if that fails, fall back to normal flags
    EINTR_LOOP(fd, ::shm_open(key.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600));
    if (fd == -1)
        EINTR_LOOP(fd, ::shm_open(key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
#else
    EINTR_LOOP(fd, ::shm_open(key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
#endif
    if (fd == -1)
    {
        const int errorNumber = errno;
        return false;
    }

    // the size may only be set once
    int ret;
    EINTR_LOOP(ret, ::ftruncate(fd, size));
    if (ret == -1)
    {
        safe_close(fd);
        return false;
    }

    safe_close(fd);

    return true;
}

bool SharedMemoryImpl_Posix::Attach(int permissions)
{
    int oflag = permissions;
    const int omode = (permissions == O_RDONLY ? 0400 : 0600);

    // First try with O_CLOEXEC flag, if that fails, fall back to normal flags
    EINTR_LOOP(hand, ::shm_open(key.c_str(), oflag | O_CLOEXEC, omode));
    if (hand == -1)
        EINTR_LOOP(hand, ::shm_open(key.c_str(), oflag, omode));

    if (hand == -1)
    {
        return false;
    }

    // grab the size
    struct stat st;
    if (::fstat(hand, &st) == -1)
    {
        CleanHandle();
        return false;
    }
    size = st.st_size;

    // grab the memory
    const int mprot = (permissions == O_RDONLY ? PROT_READ : PROT_READ | PROT_WRITE);
    memory = ::mmap(0, size, mprot, MAP_SHARED, hand, 0);
    if (memory == MAP_FAILED || !memory)
    {
        CleanHandle();
        memory = 0;
        size = 0;
        return false;
    }

#ifdef F_ADD_SEALS
    // Make sure the shared memory region will not shrink
    // otherwise someone could cause SIGBUS on us.
    // (see http://lwn.net/Articles/594919/)
    fcntl(hand, F_ADD_SEALS, F_SEAL_SHRINK);
#endif

    return true;
}

bool SharedMemoryImpl_Posix::Detach()
{
    if (!memory)
    {
        return true;
    }

    // detach from the memory segment
    if (::munmap(memory, size) == -1)
    {
        return false;
    }
    memory = 0;
    size = 0;

#ifdef __QNXNTO__
    // On QNX the st_nlink field of struct stat contains the number of
    // active shm_open() connections to the shared memory file, so we
    // can use it to automatically clean up the file once the last
    // user has detached from it.

    // get the number of current attachments
    int shm_nattch = 0;
    struct stat st;
    if (::fstat(hand, &st) == 0)
    {
        // subtract 2 from linkcount: one for our own open and one for the dir entry
        shm_nattch = st.st_nlink - 2;
    }

    CleanHandle();

    // if there are no attachments then unlink the shared memory
    if (shm_nattch == 0)
    {
        if (::shm_unlink(key.c_str()) == -1 && errno != ENOENT)
        {
            // shm_unlink error
        }
    }
#else
    // On non-QNX systems (tested Linux and Haiku), the st_nlink field is always 1,
    // so we'll simply leak the shared memory files.
    CleanHandle();
#endif

    return true;
}

bool SharedMemoryImpl_Posix::IsAttached() const
{
    return memory != nullptr && size > 0;
}

int SharedMemoryImpl_Posix::Write(unsigned char *data, size_t size)
{
    auto buflen = this->size < size ? this->size : size;
    Lock();
    memcpy(memory, data, buflen);
    Unlock();
    return buflen;
}

int SharedMemoryImpl_Posix::Read(unsigned char *buf, size_t size)
{
    auto buflen = this->size < size ? this->size : size;
    Lock();
    memcpy(buf, this->memory, buflen);
    Unlock();
    return buflen;
}

int SharedMemoryImpl_Posix::Handle()
{
    if (key.empty())
    {
        return 0;
    }
    return 1;
}

bool SharedMemoryImpl_Posix::CleanHandle()
{
    if (hand > 0)
    {
        safe_close(hand);
    }
    hand = -1;
    return true;
}

std::shared_ptr<SharedMemoryImpl> SharedMemoryImpl::CreateImpl(const std::string &key)
{
    return std::make_shared<SharedMemoryImpl_Posix>(key);
}

#endif